#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <sys/stat.h>

// 引入FFmpeg相关头文件
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48kHz 32bit audio

typedef struct {
    AVFormatContext *pFormatCtx;
    int audioStream;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame;
    AVPacket packet;
    SwrContext *swr_ctx;
    int64_t out_channel_layout;
    int out_nb_channels;
    enum AVSampleFormat out_sample_fmt;
    int out_sample_rate;
    uint8_t *out_buffer;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *hw_params;
    int frame_size;
    FILE *audio_output_file;
    int output_to_file;
} MP3Player;

/**
 * 检查是否在WSL环境中运行
 * @return 1表示在WSL环境中，0表示不在
 */
int check_wsl_environment() {
    struct stat s;
    return (stat("/proc/sys/fs/binfmt_misc/WSLInterop", &s) == 0);
}

/**
 * 初始化MP3播放器
 * @param player MP3Player结构体指针
 * @param filename MP3文件路径
 * @param output_file 输出文件名，如果为NULL则输出到ALSA设备
 * @return 0表示成功，非0表示失败
 */
int mp3_player_init(MP3Player *player, const char *filename, const char *output_file) {
    int ret;
    
    // 初始化FFmpeg库
    av_register_all();
    avformat_network_init();
    
    // 分配播放器结构体内存
    memset(player, 0, sizeof(MP3Player));
    
    // 检查是否输出到文件
    player->output_to_file = (output_file != NULL);
    player->audio_output_file = NULL;
    
    // 打开MP3文件
    if (avformat_open_input(&player->pFormatCtx, filename, NULL, NULL) != 0) {
        fprintf(stderr, "无法打开文件: %s\n", filename);
        return -1;
    }
    
    // 获取文件信息
    if (avformat_find_stream_info(player->pFormatCtx, NULL) < 0) {
        fprintf(stderr, "无法获取文件信息\n");
        return -1;
    }
    
    // 查找音频流
    player->audioStream = -1;
    for (int i = 0; i < player->pFormatCtx->nb_streams; i++) {
        if (player->pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            player->audioStream = i;
            break;
        }
    }
    
    if (player->audioStream == -1) {
        fprintf(stderr, "未找到音频流\n");
        return -1;
    }
    
    // 获取解码器参数
    AVCodecParameters *codec_params = player->pFormatCtx->streams[player->audioStream]->codecpar;
    
    // 查找解码器
    player->pCodec = avcodec_find_decoder(codec_params->codec_id);
    if (player->pCodec == NULL) {
        fprintf(stderr, "未找到解码器\n");
        return -1;
    }
    
    // 分配解码器上下文
    player->pCodecCtx = avcodec_alloc_context3(player->pCodec);
    if (player->pCodecCtx == NULL) {
        fprintf(stderr, "无法分配解码器上下文\n");
        return -1;
    }
    
    // 从编解码器参数填充解码器上下文
    if (avcodec_parameters_to_context(player->pCodecCtx, codec_params) < 0) {
        fprintf(stderr, "无法从编解码器参数填充解码器上下文\n");
        return -1;
    }
    
    // 打开解码器
    if (avcodec_open2(player->pCodecCtx, player->pCodec, NULL) < 0) {
        fprintf(stderr, "无法打开解码器\n");
        return -1;
    }
    
    // 分配帧内存
    player->pFrame = av_frame_alloc();
    if (player->pFrame == NULL) {
        fprintf(stderr, "无法分配帧内存\n");
        return -1;
    }
    
    // 设置音频重采样参数
    player->out_channel_layout = AV_CH_LAYOUT_STEREO;
    player->out_nb_channels = av_get_channel_layout_nb_channels(player->out_channel_layout);
    player->out_sample_fmt = AV_SAMPLE_FMT_S16;
    player->out_sample_rate = player->pCodecCtx->sample_rate;
    
    // 分配输出缓冲
    player->out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
    
    // 创建重采样上下文
    player->swr_ctx = swr_alloc();
    if (!player->swr_ctx) {
        fprintf(stderr, "无法创建重采样上下文\n");
        return -1;
    }
    
    // 配置重采样参数
    av_opt_set_int(player->swr_ctx, "in_channel_layout", player->pCodecCtx->channel_layout, 0);
    av_opt_set_int(player->swr_ctx, "in_sample_rate", player->pCodecCtx->sample_rate, 0);
    av_opt_set_sample_fmt(player->swr_ctx, "in_sample_fmt", player->pCodecCtx->sample_fmt, 0);
    
    av_opt_set_int(player->swr_ctx, "out_channel_layout", player->out_channel_layout, 0);
    av_opt_set_int(player->swr_ctx, "out_sample_rate", player->out_sample_rate, 0);
    av_opt_set_sample_fmt(player->swr_ctx, "out_sample_fmt", player->out_sample_fmt, 0);
    
    // 初始化重采样上下文
    if (swr_init(player->swr_ctx) < 0) {
        fprintf(stderr, "无法初始化重采样上下文\n");
        return -1;
    }
    
    // 初始化输出方式
    if (player->output_to_file) {
        // 输出到文件
        player->audio_output_file = fopen(output_file, "wb");
        if (player->audio_output_file == NULL) {
            fprintf(stderr, "无法打开输出文件: %s\n", output_file);
            return -1;
        }
        printf("音频将输出到文件: %s\n", output_file);
    } else {
        // 检查是否在WSL环境中
        if (check_wsl_environment()) {
            fprintf(stderr, "警告: 在WSL环境中运行，可能无法访问ALSA音频设备\n");
            fprintf(stderr, "建议使用 -o 参数将音频输出到文件\n");
        }
        
        // 初始化ALSA音频输出
        int rc;
        snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
        const char *device = "default";
        
        // 打开PCM设备
        rc = snd_pcm_open(&player->pcm_handle, device, stream, 0);
        if (rc < 0) {
            fprintf(stderr, "无法打开PCM设备: %s\n", snd_strerror(rc));
            fprintf(stderr, "错误原因: 可能是因为在WSL环境中没有配置音频设备，或者没有安装ALSA驱动\n");
            fprintf(stderr, "解决方案: 1. 在Linux物理机上运行 2. 使用 -o 参数将音频输出到文件\n");
            return -1;
        }
        
        // 分配硬件参数对象
        snd_pcm_hw_params_alloca(&player->hw_params);
        
        // 初始化硬件参数
        rc = snd_pcm_hw_params_any(player->pcm_handle, player->hw_params);
        if (rc < 0) {
            fprintf(stderr, "无法初始化硬件参数: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 设置访问类型
        rc = snd_pcm_hw_params_set_access(player->pcm_handle, player->hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
        if (rc < 0) {
            fprintf(stderr, "无法设置访问类型: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 设置格式
        rc = snd_pcm_hw_params_set_format(player->pcm_handle, player->hw_params, SND_PCM_FORMAT_S16_LE);
        if (rc < 0) {
            fprintf(stderr, "无法设置格式: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 设置通道数
        rc = snd_pcm_hw_params_set_channels(player->pcm_handle, player->hw_params, player->out_nb_channels);
        if (rc < 0) {
            fprintf(stderr, "无法设置通道数: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 设置采样率
        unsigned int sample_rate = player->out_sample_rate;
        rc = snd_pcm_hw_params_set_rate_near(player->pcm_handle, player->hw_params, &sample_rate, NULL);
        if (rc < 0) {
            fprintf(stderr, "无法设置采样率: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 应用硬件参数
        rc = snd_pcm_hw_params(player->pcm_handle, player->hw_params);
        if (rc < 0) {
            fprintf(stderr, "无法应用硬件参数: %s\n", snd_strerror(rc));
            return -1;
        }
        
        // 获取周期大小
        snd_pcm_uframes_t frames;
        rc = snd_pcm_hw_params_get_period_size(player->hw_params, &frames, NULL);
        if (rc < 0) {
            fprintf(stderr, "无法获取周期大小: %s\n", snd_strerror(rc));
            return -1;
        }
        
        player->frame_size = frames;
    }
    
    return 0;
}

/**
 * 播放MP3文件
 * @param player MP3Player结构体指针
 * @return 0表示成功，非0表示失败
 */
int mp3_player_play(MP3Player *player) {
    int ret;
    int data_size;
    int64_t out_samples;
    int bytes_written;
    
    // 读取音频包
    while (av_read_frame(player->pFormatCtx, &player->packet) >= 0) {
        // 检查是否是音频流
        if (player->packet.stream_index == player->audioStream) {
            // 发送包到解码器
            ret = avcodec_send_packet(player->pCodecCtx, &player->packet);
            if (ret < 0) {
                fprintf(stderr, "发送包到解码器失败\n");
                break;
            }
            
            // 接收解码后的帧
            while (ret >= 0) {
                ret = avcodec_receive_frame(player->pCodecCtx, player->pFrame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    fprintf(stderr, "接收解码帧失败\n");
                    return -1;
                }
                
                // 计算输出样本数
                out_samples = av_rescale_rnd(swr_get_delay(player->swr_ctx, player->pCodecCtx->sample_rate) + 
                                            player->pFrame->nb_samples, player->out_sample_rate, 
                                            player->pCodecCtx->sample_rate, AV_ROUND_UP);
                
                // 重采样音频数据
                ret = swr_convert(player->swr_ctx, &player->out_buffer, out_samples, 
                                (const uint8_t **)player->pFrame->data, player->pFrame->nb_samples);
                
                if (ret < 0) {
                    fprintf(stderr, "音频重采样失败\n");
                    return -1;
                }
                
                // 计算数据大小
                data_size = av_samples_get_buffer_size(NULL, player->out_nb_channels, ret, 
                                                    player->out_sample_fmt, 1);
                
                // 输出音频数据
                if (player->output_to_file) {
                    // 写入到文件
                    fwrite(player->out_buffer, 1, data_size, player->audio_output_file);
                } else {
                    // 播放到ALSA设备
                    bytes_written = snd_pcm_writei(player->pcm_handle, player->out_buffer, ret);
                    if (bytes_written < 0) {
                        // 处理xrun错误
                        if (bytes_written == -EPIPE) {
                            fprintf(stderr, "发生xrun错误，尝试恢复\n");
                            snd_pcm_prepare(player->pcm_handle);
                        } else {
                            fprintf(stderr, "写入PCM数据失败: %s\n", snd_strerror(bytes_written));
                            return -1;
                        }
                    } else if (bytes_written < ret) {
                        fprintf(stderr, "写入PCM数据不完整: 写入%d帧，期望%d帧\n", bytes_written, ret);
                    }
                }
            }
        }
        
        // 释放包内存
        av_packet_unref(&player->packet);
    }
    
    // 播放完成后，处理剩余数据
    if (!player->output_to_file) {
        snd_pcm_drain(player->pcm_handle);
    }
    
    return 0;
}

/**
 * 清理MP3播放器资源
 * @param player MP3Player结构体指针
 */
void mp3_player_cleanup(MP3Player *player) {
    // 关闭输出文件
    if (player->audio_output_file) {
        fclose(player->audio_output_file);
    }
    
    // 关闭ALSA设备
    if (player->pcm_handle) {
        snd_pcm_close(player->pcm_handle);
    }
    
    // 释放重采样上下文
    if (player->swr_ctx) {
        swr_free(&player->swr_ctx);
    }
    
    // 释放输出缓冲
    if (player->out_buffer) {
        av_free(player->out_buffer);
    }
    
    // 释放帧内存
    if (player->pFrame) {
        av_frame_free(&player->pFrame);
    }
    
    // 关闭解码器
    if (player->pCodecCtx) {
        avcodec_close(player->pCodecCtx);
        avcodec_free_context(&player->pCodecCtx);
    }
    
    // 关闭格式上下文
    if (player->pFormatCtx) {
        avformat_close_input(&player->pFormatCtx);
    }
}

/**
 * 显示帮助信息
 */
void show_help(const char *program_name) {
    printf("MP3播放器使用帮助\n");
    printf("用法: %s [选项] <mp3文件名>\n\n", program_name);
    printf("选项:\n");
    printf("  -o <输出文件>    将音频输出到文件而不是ALSA设备\n");
    printf("  -h               显示此帮助信息\n\n");
    printf("示例:\n");
    printf("  %s music.mp3              # 播放MP3文件\n", program_name);
    printf("  %s -o output.pcm music.mp3   # 将MP3解码为PCM文件\n\n", program_name);
    printf("注意: 在WSL环境中，建议使用-o参数将音频输出到文件\n");
}

/**
 * 主函数
 * @param argc 参数个数
 * @param argv 参数列表
 * @return 0表示成功，非0表示失败
 */
int main(int argc, char *argv[]) {
    MP3Player player;
    int ret;
    const char *output_file = NULL;
    int opt;
    
    // 解析命令行参数
    while ((opt = getopt(argc, argv, "o:h")) != -1) {
        switch (opt) {
            case 'o':
                output_file = optarg;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                show_help(argv[0]);
                return -1;
        }
    }
    
    // 检查是否提供了MP3文件名
    if (optind >= argc) {
        fprintf(stderr, "错误: 未提供MP3文件名\n");
        show_help(argv[0]);
        return -1;
    }
    
    // 初始化播放器
    printf("正在初始化播放器...\n");
    ret = mp3_player_init(&player, argv[optind], output_file);
    if (ret != 0) {
        fprintf(stderr, "播放器初始化失败\n");
        return -1;
    }
    
    // 播放MP3文件
    printf("开始处理: %s\n", argv[optind]);
    ret = mp3_player_play(&player);
    if (ret != 0) {
        fprintf(stderr, "播放失败\n");
        mp3_player_cleanup(&player);
        return -1;
    }
    
    // 清理资源
    printf("播放完成\n");
    mp3_player_cleanup(&player);
    
    return 0;
}